package com.beerandjesus.sportofkings.handicapper.profile.resolver;

import com.beerandjesus.sportofkings.handicapper.model.brisnet.singlefile.Horse;
import com.beerandjesus.sportofkings.handicapper.model.brisnet.singlefile.HorseRace;
import com.beerandjesus.sportofkings.handicapper.model.brisnet.singlefile.PastPerformance;
import com.beerandjesus.sportofkings.handicapper.model.brisnet.singlefile.Pedigree;
import com.beerandjesus.sportofkings.handicapper.profile.Profile;
import com.beerandjesus.sportofkings.handicapper.strategy.RacingCalculator;
import org.springframework.stereotype.Component;

import java.util.List;

import static com.beerandjesus.sportofkings.handicapper.profile.ProfileBuilder.analysisProfile;
import static com.beerandjesus.sportofkings.handicapper.profile.ProfileType.BRISNET_PEDIGREE_RATING;

/**
 * This profile attempts to identify the following scenarios:
 * <p/>
 * - Trying turf for the 1st time
 * - Turf pedigree rating higher than dirt pedigree rating
 * <p/>
 * OR
 * <p/>
 * - Dirt race
 * - Today's distance is considerably shorter or longer than previous starts
 * - Distance pedigree rating is higher than dirt pedigree rating
 * <p/>
 * Higher scores are given to runners whose pedigree rating is highest among
 * all other qualifying horses in the race.
 */
@Component
public class PedigreeProfileResolver implements ProfileResolver {

    private static final int TWO_FURLONGS_IN_YARDS = RacingCalculator.ONE_FURLONG_IN_YARDS.intValue() * 2;

    public Profile resolve(HorseRace race, Horse horse) {
        int score = 0;
        if (race.getRace().isTurf()) {
            if (turfPedigreeProfileAppliesTo(horse)) {
                // Higher score goes to runner with highest rating among all other qualified horses
                if (turfRatingIsHigherThanCompetition(horse, race.getHorses())) {
                    score = 5;
                }
                return analysisProfile(BRISNET_PEDIGREE_RATING, score);
            }
        } else if (race.getRace().isDirtOrArtificial()) {
            if (distanceOnDirtPedigreeApplies(horse, race)) {
                // Higher score goes to runner with highest rating among all other qualified horses
                if (distanceRatingIsHigherThanCompetition(horse, race)) {
                    score = 5;
                }
                return analysisProfile(BRISNET_PEDIGREE_RATING, score);
            }
        }
        return null;
    }

    private boolean distanceOnDirtPedigreeApplies(Horse horse, HorseRace race) {
        // Must have a distance rating higher than dirt rating
        if (!horse.hasMeasurableDirtDistancePedigreeRating()
                || (distanceRating(horse) <= dirtRating(horse))) {
            return false;
        }
        // Distance must be considerably longer or shorter than previous starts
        Integer distance;
        try {
            distance = Integer.valueOf(race.getRace().getDistance());
        } catch (NumberFormatException e) {
            return false;
        }
        return !anyDirtDistancesAreWithinTwoFurlongs(distance, horse);
    }

    private boolean turfPedigreeProfileAppliesTo(Horse horse) {
        // Must have turf pedigree rating higher than dirt rating
        if (!horse.hasMeasurableTurfPedigreeRating() || (turfRating(horse) <= dirtRating(horse))) {
            return false;
        }
        // Must be 1st try on turf
        return horse.isFirstTimeStarter() || hasNoTurfRaces(horse.getPastPerformances());
    }

    private boolean anyDirtDistancesAreWithinTwoFurlongs(Integer distance, Horse horse) {
        if (horse.isFirstTimeStarter()) {
            return false;
        }
        List<PastPerformance> pastPerformances = horse.getPastPerformances();
        for (PastPerformance pp : pastPerformances) {
            if (pp.getSurface().isTurf()) continue;
            try {
                Integer ppDistance = distance(pp.getDistance());
                if (Math.max(ppDistance, distance) - Math.min(ppDistance, distance) < TWO_FURLONGS_IN_YARDS) {
                    return true;
                }
            } catch (NumberFormatException e) {
                // Ignore
            }
        }
        return false;
    }

    private static Integer distance(String distance) throws NumberFormatException {
        return Integer.valueOf(distance);
    }

    private boolean turfRatingIsHigherThanCompetition(Horse horse, List<Horse> horses) {
        Double rating = turfRating(horse);
        for (Horse h : horses) {
            if (!h.equals(horse) && turfPedigreeProfileAppliesTo(h) && (turfRating(h) >= rating)) {
                return false;
            }
        }
        return true;
    }

    private boolean distanceRatingIsHigherThanCompetition(Horse horse, HorseRace race) {
        List<Horse> horses = race.getHorses();
        Double rating = distanceRating(horse);
        for (Horse h : horses) {
            if (!h.equals(horse) && distanceOnDirtPedigreeApplies(h, race) && distanceRating(h) >= rating) {
                return false;
            }
        }
        return true;
    }

    private static Double turfRating(Horse horse) {
        try {
            Pedigree pedigree = horse.getPedigree();
            if (pedigree == null || pedigree.getTurfPedigreeRating() == null) throw new NumberFormatException();
            return Double.parseDouble(stripNotation(pedigree.getTurfPedigreeRating()));
        } catch (NumberFormatException e) {
            return 0d;
        }
    }

    private static Double distanceRating(Horse horse) {
        try {
            Pedigree pedigree = horse.getPedigree();
            if (pedigree == null || pedigree.getDistancePedigreeRating() == null) throw new NumberFormatException();
            return Double.parseDouble(stripNotation(pedigree.getDistancePedigreeRating()));
        } catch (NumberFormatException e) {
            return 0d;
        }
    }

    private static Double dirtRating(Horse horse) {
        try {
            Pedigree pedigree = horse.getPedigree();
            if (pedigree == null || pedigree.getDirtPedigreeRating() == null) throw new NumberFormatException();
            return Double.valueOf(stripNotation(pedigree.getDirtPedigreeRating()));
        } catch (NumberFormatException e) {
            return 0d;
        }
    }

    private static String stripNotation(String rating) {
        rating = rating.replace("?", "");
        rating = rating.replace("*", "");
        return rating;
    }

    private boolean hasNoTurfRaces(List<PastPerformance> pps) {
        for (PastPerformance pp : pps) {
            if (pp.getSurface() != null && pp.getSurface().isTurf()) {
                return false;
            }
        }
        return true;
    }
}
